/****************************************************************************/
/*  File:       PackageDescriptorFactory.java                               */
/*  Author:     F. Georges - fgeorges.org                                   */
/*  Date:       2009-06-15                                                  */
/*  Tags:                                                                   */
/*      Copyright (c) 2009 Florent Georges (see end of file.)               */
/* ------------------------------------------------------------------------ */


package org.expath.pkg.exist;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * Build a {@link PackageDescriptor} from a package descriptor.
 *
 * @author Florent Georges
 * @date   2009-06-15
 */
public class PackageDescriptorFactory
        implements ContentHandler
{
    public PackageDescriptor read(File f)
            throws PackageException
    {
        try {
            XMLReader reader = XMLReaderFactory.createXMLReader();
            reader.setContentHandler(this);
            InputSource src = new InputSource(new FileInputStream(f));
            reader.parse(src);
            return myPkg;
        }
        catch ( FileNotFoundException ex ) {
            throw new PackageException("Package descriptor not found: " + f, ex);
        }
        catch ( IOException ex ) {
            throw new PackageException("Error reading the package descriptor: " + f, ex);
        }
        catch ( SAXException ex ) {
            throw new PackageException("Error parsing the package descriptor: " + f, ex);
        }
    }

    public void setDocumentLocator(Locator locator)
    {
        // TODO: Should use it to report errors...
    }

    public void startDocument()
            throws SAXException
    {
        // nothing
    }

    public void endDocument()
            throws SAXException
    {
        // nothing
    }

    public void startPrefixMapping(String prefix, String uri)
            throws SAXException
    {
        // nothing
    }

    public void endPrefixMapping(String prefix)
            throws SAXException
    {
        // nothing
    }

    public void startElement(String uri, String local, String qname, Attributes atts)
            throws SAXException
    {
        if ( myCatalog != null ) {
            myCatalog.append("<");
            myCatalog.append(local);
            addAttributes(atts);
            myCatalog.append(">");
        }
        else if ( PKG_NS.equals(uri) ) {
            if ( myWithinExist ) {
                startExistElement(uri, local, qname, atts);
            }
            else {
                startPkgElement(uri, local, qname, atts);
            }
        }
        else if ( CATALOG_NS.equals(uri) && "catalog".equals(local) ) {
            // TODO: Use a dedicated content handler to serialize to a byte
            // array.
            myCatalog = new StringBuilder();
            myCatalog.append("<catalog xmlns='");
            myCatalog.append(CATALOG_NS);
            myCatalog.append("'");
            addAttributes(atts);
            myCatalog.append(">");
        }
        else {
            throw new SAXException("Unknown element: {" + uri + "}" + local);
        }
    }

    public void startPkgElement(String uri, String local, String qname, Attributes atts)
            throws SAXException
    {
        if ( "package".equals(local) ) {
            myPkg = new PackageDescriptor();
        }
        else if ( "module".equals(local) ) {
            myCurrModule = new ModuleDescriptor();
            myPkg.add(myCurrModule);
            String ver = atts.getValue("", "version");
            if ( ver == null ) {
                throw new SAXException("pkg:module/@version is not there");
            }
            myCurrModule.setVersion(ver);
            String name = atts.getValue("", "name");
            if ( name == null ) {
                throw new SAXException("pkg:module/@name is not there");
            }
            myCurrModule.setName(name);
        }
        else if ( "xquery".equals(local) ) {
            myCurrQuery = new QueryDescriptor();
            myCurrModule.addQuery(myCurrQuery);
        }
        else if ( "file".equals(local)
                || "title".equals(local)
                || "home".equals(local)
                || "href".equals(local)
                || "namespace".equals(local)
                || "catalog-prefix".equals(local) ) {
            myBuffer = new StringBuilder();
        }
        else if ( "exist".equals(local) ) {
            myWithinExist = true;
        }
        else {
            throw new SAXException("Unknown element: {" + uri + "}" + local);
        }
    }

    public void startExistElement(String uri, String local, String qname, Attributes atts)
            throws SAXException
    {
        if ( "namespace".equals(local)
            || "file".equals(local)
            || "class".equals(local) ) {
            myBuffer = new StringBuilder();
        }
        else if ( "xquery".equals(local) ) {
            myCurrQuery = new QueryDescriptor();
            myCurrModule.addQuery(myCurrQuery);
        }
        else if ( "module".equals(local) ) {
            myCurrJava = new JavaModuleDescriptor();
            myCurrModule.addJavaModule(myCurrJava);
        }
        else if ( "jar".equals(local) ) {
            String file = atts.getValue("", "file");
            if ( file == null ) {
                throw new SAXException("pkg:exist/pkg:jar/@file is not there");
            }
            myCurrModule.addJar(file);
        }
        else {
            throw new SAXException("Unknown element: {" + uri + "}" + local + " (within <exist>)");
        }
    }

    private void addAttributes(Attributes atts)
    {
        for ( int i = atts.getLength() - 1; i >= 0; --i ) {
            myCatalog.append(" ");
            myCatalog.append(atts.getQName(i));
            myCatalog.append("='");
            // should escape...
            myCatalog.append(atts.getValue(i));
            myCatalog.append("'");
        }
    }

    public void endElement(String uri, String local, String qname)
            throws SAXException
    {
        if ( myCatalog != null ) {
            myCatalog.append("</");
            myCatalog.append(local);
            myCatalog.append(">");
            if ( "catalog".equals(local) ) {
                myCatalog.append("\n");
                String cat = myCatalog.toString();
                myCatalog = null;
                try {
                    myCurrModule.setCatalog(cat.getBytes("utf-8"));
                }
                catch ( UnsupportedEncodingException ex ) {
                    // should not happen
                    throw new SAXException(ex);
                }
            }
        }
        else if ( ! PKG_NS.equals(uri) ) {
            // nothing
        }
        else if ( myWithinExist ) {
            endExistElement(uri, local, qname);
        }
        else {
            endPkgElement(uri, local, qname);
        }
    }

    public void endPkgElement(String uri, String local, String qname)
            throws SAXException
    {
        if ( "package".equals(local) ) {
            // nothing
        }
        else if ( "module".equals(local) ) {
            myCurrModule = null;
        }
        else if ( "xquery".equals(local) ) {
            myCurrQuery = null;
        }
        else if ( "import-uri".equals(local) ) {
            myCurrQuery.setNamespace(myBuffer.toString());
            myBuffer = null;
        }
        else if ( "file".equals(local) ) {
            myCurrQuery.setFile(myBuffer.toString());
            myBuffer = null;
        }
        else if ( "title".equals(local) ) {
            myCurrModule.setTitle(myBuffer.toString());
            myBuffer = null;
        }
        else if ( "namespace".equals(local) ) {
            myCurrQuery.setNamespace(myBuffer.toString());
            myBuffer = null;
        }
        else if ( "catalog-prefix".equals(local) ) {
            myCurrModule.setCatalogPrefix(myBuffer.toString());
            myBuffer = null;
        }
        else {
            throw new SAXException("Unknown element: {" + uri + "}" + local);
        }
    }

    public void endExistElement(String uri, String local, String qname)
            throws SAXException
    {
        if ( "jar".equals(local) ) {
            // nothing
        }
        else if ( "namespace".equals(local) ) {
            if ( myCurrQuery == null ) {
                myCurrJava.setNamespace(myBuffer.toString());
            }
            else {
                myCurrQuery.setNamespace(myBuffer.toString());
            }
            myBuffer = null;
        }
        else if ( "file".equals(local) ) {
            myCurrQuery.setFile(myBuffer.toString());
            myBuffer = null;
        }
        else if ( "class".equals(local) ) {
            myCurrJava.setClassName(myBuffer.toString());
            myBuffer = null;
        }
        else if ( "xquery".equals(local) ) {
            myCurrQuery = null;
        }
        else if ( "module".equals(local) ) {
            myCurrJava = null;
        }
//        else if ( "function".equals(local) ) {
//            myCurrModule.addFunctionClass(myBuffer.toString());
//            myBuffer = null;
//        }
//        else if ( "library".equals(local) ) {
//            myCurrModule.addLibraryClass(myBuffer.toString());
//            myBuffer = null;
//        }
        else if ( "exist".equals(local) ) {
            myWithinExist = false;
        }
        else {
            throw new SAXException("Unknown element: {" + uri + "}" + local + " (within <exist>)");
        }
    }

    public void characters(char[] ch, int start, int length)
            throws SAXException
    {
        if ( myCatalog != null ) {
            myCatalog.append(ch, start, length);
        }
        else if ( myBuffer != null ) {
            myBuffer.append(ch, start, length);
        }
        else {
            // TODO: Check if there is any non-whitespace characters
        }
    }

    public void ignorableWhitespace(char[] ch, int start, int length)
            throws SAXException
    {
        if ( myCatalog != null ) {
            myCatalog.append(ch, start, length);
        }
    }

    public void processingInstruction(String target, String data)
            throws SAXException
    {
        if ( myCatalog != null ) {
            myCatalog.append("<?");
            myCatalog.append(target);
            myCatalog.append(" ");
            myCatalog.append(data);
            myCatalog.append("?>");
        }
    }

    public void skippedEntity(String name)
            throws SAXException
    {
        throw new SAXException("Unknown entity: " + name);
    }

    private PackageDescriptor    myPkg;
    private ModuleDescriptor     myCurrModule;
    private QueryDescriptor      myCurrQuery;
    private JavaModuleDescriptor myCurrJava;
    private StringBuilder        myBuffer;
    private StringBuilder        myCatalog;
    private boolean              myWithinExist = false;
    private static final String  PKG_NS     = "http://expath.org/mod/expath-pkg";
    private static final String  CATALOG_NS = "urn:oasis:names:tc:entity:xmlns:xml:catalog";
}


/* ------------------------------------------------------------------------ */
/*  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               */
/*                                                                          */
/*  The contents of this file are subject to the Mozilla Public License     */
/*  Version 1.0 (the "License"); you may not use this file except in        */
/*  compliance with the License. You may obtain a copy of the License at    */
/*  http://www.mozilla.org/MPL/.                                            */
/*                                                                          */
/*  Software distributed under the License is distributed on an "AS IS"     */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See    */
/*  the License for the specific language governing rights and limitations  */
/*  under the License.                                                      */
/*                                                                          */
/*  The Original Code is: all this file.                                    */
/*                                                                          */
/*  The Initial Developer of the Original Code is Florent Georges.          */
/*                                                                          */
/*  Contributor(s): none.                                                   */
/* ------------------------------------------------------------------------ */
